package pgcoin

import (
	"context"
	"fmt"
	"gitee.com/bobo-rs/creative-framework/pkg/algorithm/uniquecode"
	"gitee.com/bobo-rs/creative-framework/pkg/utils"
	"gitee.com/bobo-rs/innovideo-services/consts"
	"gitee.com/bobo-rs/innovideo-services/enums"
	"gitee.com/bobo-rs/innovideo-services/framework/dao"
	"gitee.com/bobo-rs/innovideo-services/framework/model"
	"gitee.com/bobo-rs/innovideo-services/framework/model/entity"
	"gitee.com/bobo-rs/innovideo-services/framework/service"
	"gitee.com/bobo-rs/innovideo-services/framework/validate"
	"gitee.com/bobo-rs/innovideo-services/library/exception"
	"gitee.com/bobo-rs/innovideo-services/library/services/pgcoins"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gtime"
	"time"
)

// CreateRechargePGCoinTryLock 创建充值盘古币订单数据-非阻塞锁
func (c *sPgCoin) CreateRechargePGCoinTryLock(ctx context.Context, in model.PGCoinRechargeInput) (out *model.PGCoinRechargeOutput, err error) {
	out = &model.PGCoinRechargeOutput{}
	// 处理数据-接入用户安全控制频率
	err = pgcoins.New().PGCRechargeTryLockFunc(ctx, service.BizCtx().GetUid(ctx), func(ctx context.Context) error {
		out, err = c.CreateRechargePGCoin(ctx, in)
		return err
	})
	return
}

// CreateRechargePGCoin 创建充值盘古币订单数据
func (c *sPgCoin) CreateRechargePGCoin(ctx context.Context, in model.PGCoinRechargeInput) (out *model.PGCoinRechargeOutput, err error) {
	// 用户信息
	uid := service.BizCtx().GetUid(ctx)

	// 验证规则
	config, err := pgcoins.New().ValidatorRechargeRule(ctx, uid, in)
	if err != nil {
		return nil, err
	}

	// 充值记录
	recharge := entity.PanguCoinOrder{
		PanguCoin: in.RechargePGC,
		PayFee:    utils.Div(in.RechargePGC, config.PGCRate, 2),
		OrderNo:   uniquecode.UniSerialCode(ctx, consts.PanGuCoinOrderNoPrefix, true),
		Uid:       uid,
		Currency:  config.Currency,
	}

	// 创建充值账单
	_, err = dao.PanguCoinOrder.Ctx(ctx).OmitEmpty().Save(recharge)
	if err != nil {
		return nil, exception.New(`创建盘古币充值账单失败`)
	}
	out = &model.PGCoinRechargeOutput{
		OrderNo: recharge.OrderNo,
		PayFee:  recharge.PayFee,
	}
	return
}

// GetPayPGCoinDetail 获取盘古币支付信息
func (c *sPgCoin) GetPayPGCoinDetail(ctx context.Context, in model.PGCoinPayDetailInput) (out *model.PaymentResultData, err error) {
	out = &model.PaymentResultData{}
	// 获取订单信息
	item := model.PGCoinPayDetailItem{}
	err = c.OrderModel().Scan(ctx, g.Map{
		dao.PanguCoinOrder.Columns().OrderNo: in.OrderNo,
		dao.PanguCoinOrder.Columns().Uid:     service.BizCtx().GetUid(ctx),
		dao.PanguCoinOrder.Columns().Status:  enums.PGCoinOrderStatusPending,
	}, &item)
	if err != nil {
		return nil, exception.New(`盘古币充值订单不存在或已完成`)
	}

	// 是否已支付
	if item.PayStatus == enums.PGCoinOrderPayPaid {
		return nil, exception.Newf(`盘古币充值订单%s`, item.PayStatus.Fmt())
	}

	// 获取配置
	config, _ := c.Config(ctx)

	// 获取支付信息
	return &model.PaymentResultData{
		Amount:  item.PayFee,
		Content: fmt.Sprintf(`充值盘古币%d`, item.PanguCoin),
		Other: map[string]interface{}{
			`return_url`: config.ReturnUrl,
			`cancel_url`: config.CancelUrl,
		},
	}, nil
}

// CallbackUpdateCoinsPayMethod 回调更新盘古币支付方式（PayPal支付必须）
func (c *sPgCoin) CallbackUpdateCoinsPayMethod(ctx context.Context, in model.CallbackUpdateCoinsPayMethodInput) error {
	// 更新盘古币支付方式
	_, err := dao.PanguCoinOrder.Ctx(ctx).
		Where(dao.PanguCoinOrder.Columns().OrderNo, in.OrderNo).
		Where(dao.PanguCoinOrder.Columns().PayStatus, enums.PGCoinOrderPayPending).
		Update(g.Map{
			dao.PanguCoinOrder.Columns().PayCode: in.PayType,
			dao.PanguCoinOrder.Columns().PayNo:   in.PayNo,
			dao.PanguCoinOrder.Columns().PayName: in.PayType.Fmt(),
		})
	if err != nil {
		return exception.Newf(`更新盘古币账单支付方式失败%s`, err.Error())
	}
	return nil
}

// ConfirmCoinsPayment 确认盘古币支付完成（场景：第三方支付完成-跳转到支付完成页）
func (c *sPgCoin) ConfirmCoinsPayment(ctx context.Context, in model.ConfirmCoinsPaymentInput) (detail *model.PGCoinsConfirmPayDetailItem, err error) {
	// 获取盘古币订单信息
	detail = &model.PGCoinsConfirmPayDetailItem{}
	err = c.OrderModel().Scan(ctx, g.Map{
		dao.PanguCoinOrder.Columns().OrderNo: in.OrderNo,
		dao.PanguCoinOrder.Columns().Uid:     service.BizCtx().GetUid(ctx),
	}, &detail)
	if err != nil {
		return nil, err
	}
	// 是否已取消
	if detail.Status == enums.PGCoinOrderStatusCancel || detail.Status == enums.PGCoinOrderStatusRefund {
		return nil, exception.Newf(`盘古币订单%s`, detail.Status.Fmt())
	}

	// 订单已支付直接返回
	if detail.PayStatus == 1 && detail.PayAt != `` {
		return
	}

	// 确认支付（PayPal必须）
	res, err := service.Pay().ConfirmPayment(ctx, in.PayNo, in.PayType, func(ctx context.Context, tradeNo string) (interface{}, error) {
		// 若是PayPal支付，同步更新支付信息
		if in.PayType == enums.PayTypePayPal {
			err = c.NotifyPGCoin(ctx, model.PGCoinPayNotifyInput{
				PayNo:   in.PayNo,
				PayType: in.PayType,
				TradeNo: tradeNo,
			})
			if err != nil {
				// 实时同步失败，则不报错，异步Webhook会同步执行通知，返回正常即可
				g.Log().Debug(ctx, `PayPal支付同步更新支付状态`, err)
				return detail, nil
			}
			// 更新支付状态为已支付
			detail.PayStatus = enums.PGCoinOrderPayPaid
		}
		return detail, nil
	})
	if err != nil {
		return nil, err
	}
	// 返回结果
	return res.(*model.PGCoinsConfirmPayDetailItem), nil
}

// NotifyPGCoin 回调盘古币支付状态
func (c *sPgCoin) NotifyPGCoin(ctx context.Context, in model.PGCoinPayNotifyInput) error {
	var (
		detail  = model.PGCoinPayDetailItem{}
		columns = dao.PanguCoinOrder.Columns()
		where   = g.Map{}
	)

	// PayPal支付按支付单号处理
	if in.PayType == enums.PayTypePayPal {
		where[columns.PayNo] = in.PayNo
	} else {
		// 截取订单号
		where[columns.OrderNo] = service.Pay().SplitTradeNo(in.TradeNo)
	}

	// 获取订单数据
	err := c.OrderModel().Scan(ctx, where, &detail)
	if err != nil {
		return exception.New(`订单数据不存在`)
	}

	// 是否支付
	if detail.PayStatus == enums.PGCoinOrderPayPaid {
		return nil
	}

	// 订单是否待确认状态
	if detail.Status != enums.PGCoinOrderStatusPending {
		return exception.Newf(`盘古币订单状态[%s]`, detail.Status.Fmt())
	}

	// 同步更新数据
	return dao.PanguCoinOrder.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 生成盘古币账单
		if err = c.GenPGCoinBill(ctx, model.PGCoinRechargeBillInput{
			OrderId:           detail.Id,
			Uid:               detail.Uid,
			RechargePanguCoin: detail.PanguCoin,
			Type:              enums.PGCoinBillTypeRecharge,
		}); err != nil {
			return err
		}

		// 更新盘古币支付状态
		_, err = dao.PanguCoinOrder.Ctx(ctx).
			Where(dao.PanguCoinOrder.Columns().Id, detail.Id).
			Update(g.Map{
				columns.PayStatus: enums.PGCoinOrderPayPaid,
				columns.TradeNo:   in.TradeNo,
				columns.PayCode:   in.PayType,
				columns.PayName:   in.PayType.Fmt(),
				columns.PayNo:     in.PayNo,
				columns.PayAt:     time.Now().Format(time.DateTime),
				columns.Status:    enums.PGCoinOrderStatusSuccess,
			})
		if err != nil {
			return exception.New(`更新盘古币充值订单支付状态失败`)
		}
		return nil
	})
}

// GiveUserPGCoin 给用户账户赠送盘古币
func (c *sPgCoin) GiveUserPGCoin(ctx context.Context, uid, coins uint, t enums.PGCoinBillType) error {
	// 赠送盘古币
	return c.GenPGCoinBillTX(ctx, model.PGCoinRechargeBillInput{
		Uid:               uid,
		RechargePanguCoin: coins,
		Type:              t,
	})
}

// GenPGCoinBillTX 生成盘古币账单-事务处理
func (c *sPgCoin) GenPGCoinBillTX(ctx context.Context, in model.PGCoinRechargeBillInput) error {
	// 事务处理
	return dao.PanguCoinBill.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		return c.GenPGCoinBill(ctx, in)
	})
}

// GenPGCoinConsumeBillTX 生成消费盘古币账单-事务处理
func (c *sPgCoin) GenPGCoinConsumeBillTX(ctx context.Context, in model.PGCoinConsumeInput) error {
	// 事务处理
	return dao.PanguCoinBill.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		return c.GenPGCoinConsumeBill(ctx, in)
	})
}

// GenPGCoinBill 生成盘古币账单数据
func (c *sPgCoin) GenPGCoinBill(ctx context.Context, in model.PGCoinRechargeBillInput) error {
	// 验证生成参数
	if err := validate.CheckGenCoinsBillParam(in); err != nil {
		return err
	}

	// 盘古币充值配置
	config, err := c.Config(ctx)
	if err != nil {
		return err
	}

	// 生成盘古币流水
	transactionNo, err := c.GenPGCoinTransaction(ctx, in.Uid, in.RechargePanguCoin, enums.PGCoinTransactionTypeIncome)
	if err != nil {
		return err
	}

	// 创建盘古币账单
	billId, err := dao.PanguCoinBill.Ctx(ctx).
		OmitEmpty().
		InsertAndGetId(entity.PanguCoinBill{
			BillNo:             uniquecode.UniSerialCode(ctx, consts.PanGuCoinBillPrefix, true),
			Uid:                in.Uid,
			OrderId:            in.OrderId,
			TotalPanguCoin:     in.RechargePanguCoin,
			AvailablePanguCoin: in.RechargePanguCoin,
			BillType:           uint(in.Type),
			ExpiredDate:        gtime.New(config.PGCExpiredAt),
			Remark:             in.Remark,
		})
	if err != nil {
		return exception.New(`生成盘古币账单失败`)
	}

	// 生成盘古币账单明细
	item := entity.PanguCoinBillItem{
		BillId:        uint(billId),
		Uid:           in.Uid,
		TransactionNo: transactionNo,
	}
	c.processGenPGCoinBillItemParam(&item, in.RechargePanguCoin, enums.PGCoinTransactionTypeIncome)
	if err = c.GenPGCoinBillItem(ctx, item); err != nil {
		return err
	}

	// 更新盘古币可用量
	return service.User().UpdateFundsCoinByUid(ctx, in.Uid, in.RechargePanguCoin, true)
}

// GenPGCoinConsumeBill 生成盘古币消费账单
func (c *sPgCoin) GenPGCoinConsumeBill(ctx context.Context, in model.PGCoinConsumeInput) error {
	// 获取用户可用的资金和账单
	funds, bill, err := c.GetUserAvailableFundsAndBill(ctx, in.Uid)
	if err != nil {
		return err
	}

	// 验证可用盘古币
	err = validate.CheckUserAvailableCoins(model.CheckAvailableCoins{
		ConsumedCoins:      in.ConsumePGCoin,
		BillAvailableCoins: bill.TotalPGCoin,
		FundsCoins:         funds.AvailablePanguCoin,
	})
	if err != nil {
		return err
	}

	// 生成流水
	transactionNo, err := c.GenPGCoinTransaction(ctx, in.Uid, in.ConsumePGCoin, enums.PGCoinTransactionTypeConsume)
	if err != nil {
		return err
	}

	// 生成账单明细
	err = c.processAndUpdateBills(ctx, model.BillItemAndUpdateBillInput{
		PGCoinConsumeInput: in,
		TransactionNo:      transactionNo,
		BillList:           bill.Rows,
	})
	if err != nil {
		return err
	}

	// 减扣掉用户余额
	return service.User().UpdateFundsCoinByUid(ctx, in.Uid, in.ConsumePGCoin, false)
}

// GetUserAvailableFundsAndBill 获取用户可用资金和账单
func (c *sPgCoin) GetUserAvailableFundsAndBill(ctx context.Context, uid uint) (funds *entity.UserFunds, bill *model.UserAvailablePGCoinListOutput, err error) {
	// 获取用户资金
	funds, err = service.User().GetFundsDetailByUid(ctx, uid)
	if err != nil {
		return nil, nil, err
	}
	// 获取账单列表
	bill, err = c.GetUserAvailablePGCoinBillList(ctx, uid)
	if err != nil {
		return nil, nil, err
	}

	return
}

// processAndUpdateBills 处理账单明细和更新账单数据
func (c *sPgCoin) processAndUpdateBills(ctx context.Context, in model.BillItemAndUpdateBillInput) (err error) {
	// 生成账单明细
	var (
		items                []entity.PanguCoinBillItem // 账单明细
		totalConsumedPGCoin  = in.ConsumePGCoin         // 初始化总消费盘古币
		fullyConsumedBillIds = make([]uint, 0)          // 完成抵扣的账单id集合
		remainingPGCoin      uint                       // 本次账单可全额抵扣消费盘古币并且有剩余,需更新本账单可用量
		remainingBillId      uint                       // 消费盘古币小于账单盘古币减扣剩余的盘古币账单ID
	)
	// 迭代消费账单明细
	for _, row := range in.BillList {
		// 初始化账单明细
		item := entity.PanguCoinBillItem{
			BillId:        row.Id,
			Uid:           in.Uid,
			TransactionNo: in.TransactionNo,
		}

		// 处理消费数据
		totalConsumedPGCoin, remainingPGCoin = c.processSingleBillItem(&item, row, totalConsumedPGCoin)
		items = append(items, item) // 组装数据

		// 本次账单剩余盘古币且结束账单消费
		if remainingPGCoin > 0 {
			remainingBillId = row.Id // 还有剩余盘古币ID-需要更新账单数据
			break
		}

		// 剩余全额抵扣,组装全额抵扣账单ID
		fullyConsumedBillIds = append(fullyConsumedBillIds, row.Id)
		if totalConsumedPGCoin == 0 {
			// 消费盘古币已全额抵扣，
			break
		}
	}

	// 生成盘古币账单明细
	if err = c.GenPGCoinBillItem(ctx, items...); err != nil {
		return err
	}

	// 更新账单
	if len(fullyConsumedBillIds) > 0 {
		if err = c.UpdatePGCoinBillComplete(ctx, fullyConsumedBillIds); err != nil {
			return err
		}
	}

	// 更新剩余盘古币
	if remainingBillId > 0 {
		return c.UpdateDeducePGCoinBill(ctx, remainingBillId, remainingPGCoin)
	}
	return nil
}

// processSingleBillItem 处理单个账单项
func (c *sPgCoin) processSingleBillItem(item *entity.PanguCoinBillItem, bill model.UserAvailablePGCoinItem, totalPGCoin uint) (uint, uint) {
	var (
		consumedPGCoin  = bill.AvailablePanguCoin // 账单实际消耗盘古币（默认全额消耗）
		remainingPGCoin uint                      // 账单剩余未消费的盘古币
	)
	// 账单大于等于消费
	if bill.AvailablePanguCoin >= totalPGCoin {
		// 是否大于
		if bill.AvailablePanguCoin > totalPGCoin {
			remainingPGCoin = bill.AvailablePanguCoin - totalPGCoin // 本账单剩余的盘古币=原账单盘古币减去-消费盘古币
		}
		consumedPGCoin = totalPGCoin
		totalPGCoin = 0
	} else {
		// 小于消费，总消费盘古币 -=（减等）当前账单盘古币
		totalPGCoin -= bill.AvailablePanguCoin
	}

	// 整理账单明细数据
	c.processGenPGCoinBillItemParam(item, consumedPGCoin, enums.PGCoinTransactionTypeConsume)
	return totalPGCoin, remainingPGCoin
}
